Conditionally make a page read-only using react - html

I want to create a React webpage that has both editable and read-only versions, the whole page not just a few elements on the page. A version is displayed to the user based on user id and other conditions. How do I do it?
The only straight forward way I know is to create 2 pages one editable and one read-only and based on the condition show the appropriate version (html page) to the user.
Is there a better and smarter way to do this? Like can I create just one page for both versions and toggle the mode based on the condition to the users?

Your question should have provided an example of some code you had tried but based on the description, very rough example below of one of many possible solutions.
Suppose EditView component is your page and you are able to pass a value for permission based on whatever credential you need to apply.
Then you have a component, ExampleField that takes the permission and displays either an input or static text. A collection of multiple of these fields is mapped from a theoretical array of data that you'll have to fetch from somewhere and the fields are returned by the main component.
const EditView = ({permission}) => {
const [editable, setEditable] = useState();
const [values, setValues] = useState([]);
useEffect(() => {
setEditable(permission);
}, [permission]);
useEffect(() => {
//maybe fetch your data from a back end or whatever and assign it to `values`
//on page load
}, [])
const ExampleField = ({permission, val, index}) => {
const handleChange = (e) => {
let vals = [...values];
vals[index] = val;
setValues(vals);
}
return(
<>
{permission
? <input name="example" type="text" defaultValue={val}
onChange={handleChange} />
: <span>{val}</span>}
</>
)
}
const fields = values.map((value, i) => {
return <ExampleField permission={permission} val={value} index={i}/>
})
return(
<>
{fields}
</>
)
}
Most likely, you'll want to break out various field components into their own file and, instead of using useState, you would probably want to explore useContext or useStore type functionality to lift up your state and do all the react things.
*Haven't tested or even compiled this code - for illustration purposes only.

Related

I am displaying dynamic data on this React Boostrap Slider. Is there a way to make the code more efficient?

this is my first post here. I am building a React Boostrap Carousel that pulls Movie data from the database and displays it. I am new to React and programming in general. So far i made the code work. But i do not know how to handle the images. The images are stores in React **src/assets/imgs. **. Should i store a reference to the image in the database like so ../../assets/imgs/the-batman.jpg and then display it? If so later on on the project the admin will have to create a MovieOfTheMonth. He should be able to input movie title, descrition etc, and also upload a movie image. Is there a way when the image is uploaded it, to store it to a specific folder, in this case src/assets/imgs and also create a reference in the database? I do not need the solution here, just to tell me if it is achievable. Finally is there a way to improve my code?
this is my full code for this component
import React, {useState, useEffect } from 'react';
import './Carousel.css'
import Carousel from 'react-bootstrap/Carousel';
import 'bootstrap/dist/css/bootstrap.min.css';
import axios from 'axios';
const CarouselHero = () => {
//boostrap code
const [index, setIndex] = useState(0);
const handleSelect = (selectedIndex, e) => {
setIndex(selectedIndex);
};
//Get Movies of the month
const [movie, setMovie] = useState([])
const getMovie = () => {
axios.get("http://localhost:4000/moviesOfTheMonth")
.then((res) => {
const myMovie = res.data
myMovie.push()
setMovie(myMovie);
})
}
useEffect(() => getMovie(), []);
return (
<Carousel activeIndex={index} onSelect={handleSelect} fade>
{movie.map((item) => {
const {id, title, description} = item.Movie
return (
<Carousel.Item interval={2000}>
<img
src={require("../../assets/imgs/the-batman.jpg")}
alt="First slide"
/>
<Carousel.Caption >
<h1>{title}</h1>
<p>{description}</p>
<button>Book Now</button>
</Carousel.Caption>
</Carousel.Item>
)
})}
</Carousel>
);
};
export default CarouselHero;
I think technically it is achievable to iterate over the assets folder and create database entries for new images (create and compare hash?), but it is usually not how you do it. I would put images in some file storage like S3 and reference them with id.
I don't know who the admin will be in your project, but if admin is rather a non technical person, you could create (or use a template of course) a small and simple admin dashboard, where he/she can maintain a movie of the month via UI.
FFinally some remarks on your code:
const handleSelect = (selectedIndex, e) => { setIndex(selectedIndex); }; - If you need only first, but not second, third etc. argument, you can just leave it out: (selectedIndex) => ...
const [movie, setMovie] = useState([]) - don't forget to use semicolon after every statement. (They are optional, but are useful sometimes to avoid weird errors). Also, you have a list here. So maybe better call it "movies".
myMovie.push() - What are you trying to push here?
useEffect(() => getMovie(), []); - Usually you define and call async function directly in useEffect. Don't you get any hints or warning?
movie.map((item) => { - When you iterate and get a list back React needs a key on every element (here on Carousel.Item). Don't just use the index, as it is a bad practice. Always try to find id property in your data.
const {id, title, description} = item.Movie - Why is the data nested by Movie object? Can't you just say item.id, item.title, item.description?

How is data handled with getStaticProps when component uses it in useEffect()?

I have a page that uses the getStaticProps function. The function loads a list of objects that each hold three things: the title of a book, the author of a book, and then a list of characters in the book. The page then maps each of these objects to a component that puts them in a pretty pill.
The component takes the title and author and embeds it into its HTML code. However, the one complexity is the component also uses a useEffect() hook to randomly select 1 character within the character list provided as a prop and then displays them as part of the component HTML. Since it is useEffect(), this does not happen at build time. The reason I want it to occur when the user requests the page is that each user should see a different randomly selected character (i.e., although everything else on the page is the same for all users, I want the character to be randomly selected).
My question is, can getStaticProps work here? Does it build out the HTML as best it can and then when the user requests the page, the character list data is already provided? Or because it uses useEffect(), the component will have to re-request the data from the backend? I want to limit backend requests because the data is stored in AirTable and that only allows 5 calls per second.
index.jsx:
const Home = (props) => {
return (
<div className="flex min-h-screen">
<main className="relative mx-5 mt-16">
{props.response.map((bookData) => (
<BookPill
bookTitle={bookData['Book Title']}
bookAuthor={bookData['Book Author']}
bookCharacters={bookData['Characters']}
/>
))}
</main>
</div>
)
}
export default Home
export async function getStaticProps(context) {
try {
const response = await getBookData()
return {
props: {
response,
},
revalidate: 5,
}
} catch (error) {
return {
props: {
err: 'Something went wrong 😕',
},
}
}
}
BookPill.jsx:
const BookPill = ({
bookTitle,
bookAuthor,
bookCharacters,
}: PropsType) => {
const [randomCharacter, setRandomCharacter] = useState('')
useEffect(() => {
const random_character =
bookCharacters[Math.floor(Math.random() * bookCharacters.length)]
setRandomCharacter(random_character)
}, [])
return (
<div className="my-2 grid grid-cols-1">
<div className="px-5 text-center">
<p className="mb-4">{bookTitle}</p>
<p className="text-sm">{bookAuthor}</p>
</div>
<div className="flex items-center justify-center md:col-span-1">
<div className="rounded-3xl">
{randomCharacter}
</div>
</div>
</div>
)
}
export default BookPill
To summarize what was discussed in the comments:
Because you're using getStaticProps with revalidate: 5, the data will only be fetched on the server, at most every 5 seconds. The data fetched inside getStaticProps is then passed to the HTML generated for the page - you can see the props returned from getStaticProps in the __NEXT_DATA__ script in your HTML.
When the page gets rendered on the browser, the useEffect is triggered and will use that data. No additional requests occur on the client-side.

Is it possible to copy an object's properties from one component and display them in another component using refs?

Say I have 2 components. One is a table with a list of stores. Each store has properties like color, item, open, closed. The other component is one to create a store.
I want to be able to click on a little copy icon on one of the created stores already, and take that information to the create store component, and populate that component with the properties in order to make changes and create a completely new store.
Is this doable using refs? Or is there a better way of doing this?
Use ref to this task is a mistake. React works using a Virtual DOM that is a cleaner and faster Object Tree with information that will be through to DOM by React DOM the REF API is used to access direct the DOM information, and you don't need any information from DOM to do ur task.
https://reactjs.org/docs/refs-and-the-dom.html
A way yo do what you describe is create a state/setState on the parent component and pass a state for the store component and a setState to the table component for example:
import React, { useState } from 'react'
const StoreComponenet = ({ color, item, open})=>{
// logic of component
return (
<div>
// ...
</div>
)
}
const TableComponent = ({ setStore })=>{
// logic of component
return (
<table>
<tr onClick={() => setStore("blue", {id: 2, name: "BlueStore" }, false)}>
Build blue store
</tr>
...
</table>
)
}
const App = ()=>{
const [store, setStore] = useState(null)
return (
<TableComponent setStore={setStore} />
{
store &&
<StoreComponent
color={store?.color}
item={store?.item}
open={store?.open}
/>
}
)
}

Using 2 Pages to filter a table in angular

I'm quite new to angular and wanted to know how to make it so i can have 1 page that you put the info you want to filter in the table and when you press "search" it will lead you to the second page where you see the table after its filtered.
i my question is odd but i really couldn't find any answer how to do this online.
I cant share code as its confidential to my work.
Something that looks like this site : https://maerskcontainersales.com/
I have tried using mock data but still couldn't put my head into the right thing to do.
There can be multiple ways how you can achieve this.
Using Provider
Suppose you have two pages and , serach-page is where you will enter your filters and result-page is where the table renders.
In search-page, you will create inputs( ex: textbox, dropdown etc ) and have ngModels for all of them, or you can use Angular reactive forms i.e FormGroup and FormControls. Users will select their input and click on search button, which will read values from models or controls and store them in the provider.
search-page.component.html
<form [formGroup]="searchForm" (submit)="search()">
<input formControlName="country" />
<input formControlName="city" />
...
<input type="submit">
</form>
search-page.component.ts
export class SearchPage {
...
search() {
const country = this.searchForm.get('country').value
...
// get rest of the values
...
this.searchService.setData({ country, city });
this.router.navigate(['/result']); // '/result' is path on the result-page
}
...
}
search.service.ts
#Injectable()
export class SearchService {
_data : any;
set data(val) {
this._data = val;
}
get data() {
return this._data;
}
}
result-page.component.ts
export class ResultPage {
...
ngOnInit() {
const filters = this.searchService.getData();
// filters will be your data from previous page
}
...
}
Using RouterParams
search-page.component.html
// same as before
search-page.component.ts
export class SearchPage {
...
search() {
const country = this.searchForm.get('country').value
...
// get rest of the values
...
this.router.navigate(['/result', { country, city }]); // '/result' is path on the result-page
}
...
}
result-page.component.ts
export class ResultPage {
...
constructor(route:ActivatedRoute) {
this.country = route.snapshot.paramMap.get("country")
// alternatively you can also do below
route.paramMap.subscribe(filters => {
// you will have your filters here
});
}
...
}
And once you have values of filters in result-page, use them to get data or filter data if already fetched, then render the table accordingly.
Let me know if I wasn't clear.
The simple solution I would suggest you to use a filter component and a results component a third container component. This component will get the filter criteria as an input variable and will output the filter criteria (using an output variable) when you press the "filter" button.
The container app will look like this:
<filterComponent (onFilter)="changeFilter($event)" [data]="someDate" *ngIf="!filterCriteria"></filterComponent>
<resultsComponent [data]="someDate" [filterCriteria]="filterCriteria" *ngIf="!!filterCriteria"></resultsComponent>
The filterCriteria that is sent to the second tableComponent will come from the eventEmmiter of the first tableComponent. The filterCriteria variable will be initiate to null and this will allow you to switch from one table to the other.

Read long text in Angular 2

I have a very long document - 40000 words - I would like to display in a styled manner, like html.
I need to display it with headers, paragraphs and bold styling.
I am building an Angular app. I tried loading the converted document as a local html, but it takes a very long time.
For instance, I tried this:
var html = this.http.get("../data.html").map(ref => {
console.log(html);
} );
Are there any other ways I can load this text? Maybe break it up into smaller chunks somehow?
Based on what you've provided with no other context:
You need to subscribe to the Observable otherwise, nothing will ever happen since Observable execution is lazy:
var html = this.http.get("../data.html")
.map(ref => {
console.log(html);
return ref;
})
.subscribe(ref => ...);
Also, you're using console.log(html) in your map, but html does not exist in the context of map so you would need to do something like:
var html = this.http.get("../data.html")
.map(ref => {
console.log(ref); // Does this log appear and what does it contain?
return ref;
})
.subscribe(ref => ...);
Finally, var html is an Observable not HTML so I'd probably rename this to something a bit more descriptive if you're passing it around and subscribing to the response:
const data$ = this.http.get("../data.html")
.map(ref => {
console.log(ref);
return ref;
});
// ... do other stuff
data$.subscribe(ref => ...);
Or if not passed chain it and subscribe which indicates the Observeable has completed:
this.http.get("../data.html")
.map(ref => {
console.log(ref);
return ref;
}).subscribe(ref => ...);
If this doesn't help answer the question it's because you haven't provided enough information, and I'd suggest answering:
In the second example what does console.log(ref) output?
Include more code that provides more context like do you use subscribe already and what does the data you're using look like?
Make an example in StackBlitz that replicates the issue. Just click Angular and you get a pre-made Angular application you can drop your code into and then people can hack directly on the issue. Takes tops 5 seconds to setup