Pass true/false state from a child to the parent- React - javascript

I would like to change a true/false state in a child component and pass it to a parent component, where the state is actually defined. Right now, this results only in an error.
Parent:
const PostTemplate = ({ data }) => {
const [isSlider, setIsSlider] = useState(false);
return (
<Layout class="page">
<main>
<Views setIsSlider={isSlider} {...data} />
</main>
</Layout>
)}
</>
);
};
Child:
const Views = (data) => {
return (
<div
key="views"
className="post__gallery views"
>
{data.views.edges.map(({ node: view }) => (
<divy
onClick={() => setIsSlider(true)}
>
<GatsbyImage
image={view.localFile.childImageSharp.gatsbyImageData}
/>
<div
className="post__caption"
dangerouslySetInnerHTML={{
__html: view.caption,
}}
/>
</div>
))}
</div>
);
};
Right now, this puts out:
setIsSlider is not a function.
(In 'setIsSlider(true)', 'setIsSlider' is false)
Perhaps also relevant the console.log from React:
Can't perform a React state update on an unmounted component.
This is a no-op, but it indicates a memory leak in your application.
To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.

You're passing isSlider as a prop. You should be passing setIsSlider.
Note that state management in React is a complex topic and you probably want to do some research into how it is done in general. Directly passing a state setting callback works, but it doesn't scale well to complex applications.

Instead of passing the state variable you have to pass the state function like this:
Parent
const PostTemplate = ({ data }) => {
const [isSlider, setIsSlider] = useState(false);
return (
<Layout class="page">
<main>
<Views setIsSlider={setIsSlider} {...data} />
</main>
</Layout>
)}
</>
);
};
Child
You've to use data.<function_passed_via_props> to access the setIsSlider function, like this:
const Views = (data) => {
return (
<div
key="views"
className="post__gallery views"
>
{data.views.edges.map(({ node: view }) => (
<divy
onClick={() => data.setIsSlider(true)}
>
<GatsbyImage
image={view.localFile.childImageSharp.gatsbyImageData}
/>
<div
className="post__caption"
dangerouslySetInnerHTML={{
__html: view.caption,
}}
/>
</div>
))}
</div>
);
};

To Fix: Change setIsSlider={isSlider} into setIsSlider={setIsSlider}.
However, in case you need to manage more than a few states across components. I would suggest using Redux. This can centralize your common-used states, you can access or update these states in any component using Redux.

Related

Rendering Child Component from store

I have a component which has child components, i want to render these child components with different Ids. They are getting their data from store.The problem is they are rendered but with the same item. how can this be solved?
MultiImages Component
const MultiImages: () => JSX.Element = () => {
const values = ['500', '406', '614'];
return (
<div>
{values.map((val, index) => {
return <OneImage key={index} projectID={val} />;
})}
</div>
);
};
export default MultiImages;
OneImage Component
const OneImage: () => JSX.Element = ({ projectID }) => {
const projectData = useProjectDataStore();
const { getProject } = useAction();
useEffect(() => {
getProject(projectID ?? '');
}, []);
return (
<>
<div>
<img
src={projectData.picture}
}
/>
<div>
<a>
{projectData.projectName}
</a>
</div>
</div>
</>
);
};
export default OneImage;
Your issue here - you are calling in a loop, one by one fetch your projects, and each call, as far as we can understand from your example and comments override each other.
Your are doing it implicitly, cause your fetching functionality is inside your Item Component OneImage
In general, the way you are using global state and trying to isolate one from another nodes is nice, you need to think about your selector hook.
I suggest you, to prevent rewriting too many parts of the code, to change a bit your selector "useProjectDataStore" and make it depended on "projectID".
Each load of next project with getProject might store into your global state result, but instead of overriding ALL the state object, you might want to use Map(Dictionary) as a data structure, and write a result there and use projectID as a key.
So, in your code the only place what might be change is OneImage component
const OneImage: () => JSX.Element = ({ projectID }) => {
// making your hook depended on **projectID**
const projectData = useProjectDataStore(projectID);
const { getProject } = useAction();
useEffect(() => {
// No need of usage **projectID** cause it will inherit if from useProjectDataStore
getProject();
}, []);
return (
<>
<div>
<img
src={projectData.picture}
}
/>
<div>
<a>
{projectData.projectName}
</a>
</div>
</div>
</>
);
};
export default OneImage;
And inside of your useProjectDataStore store result into a specific key using projectID.
Your component OneImage will return what's in the return statement, in your case:
<>
<div>
<img
src={projectData.picture}
/>
<div>
<a>
{projectData.projectName}
</a>
</div>
</div>
</>
This tag <></> around your element is a React.fragment and has no key. This is the reason you get this error.
Since you already have a div tag wrapping your element you can do this:
<div key={parseInt(projectID)}>
<img
src={projectData.picture}
/>
<div>
<a>
{projectData.projectName}
</a>
</div>
</div>
You can also change the key to Math.floor(Math.random() * 9999).
Note that passing the prop key={index} is unnecessary, and is not advised to use index as keys in a react list.

How to use onClick event on Card.js component to render Article.js Component in React?

Right now i am in Home.js page and i want to render Article.js component/page when user click on particular card (Card.js component). Here is my Home.js code
const Home = () => {
const posts = useSelector((state) => state.posts)
const [currentId, setCurrentId] = useState(null)
const handleClick = () => {
return <Article />
}
return (
<div className="container">
<h4 className="page-heading">LATEST</h4>
<div className="card-container">
{
posts.map(post => <Card key={post._id} post={post} setCurrentId={setCurrentId} onClick={handleClick} />)
}
</div>
</div>
)
}
ONE MORE PROBLEM :
How can I send post variable into onClick method? when i send it method is getting called.
Thank You in Advance :)
It sounds like you want to use the React Router? As I take it you want to load the post as its own page?
I should also point out that any function passed to onClick cannot return anything. The only purpose return can serve in an event function is to exit the function early.
I do agree with #Jackson that you might want to to look into React Router. But you don't need it. You can conditionally render the Article component based on the currentId.
A click handler shouldn't return anything. Instead of returning the <Article /> from the onClick callback, you would use onClick to control the currentId state. You can pass a function that sets the currentId to the post id based on the post variable in your map like this: onClick={() => setCurrentId(post._id)}.
The return for your Home component will either render the list of posts or a current post, depending on whether or not you have a currentId or just null.
const Home = () => {
const posts = useSelector((state) => state.posts);
const [currentId, setCurrentId] = useState(null);
return (
<div className="container">
{currentId === null ? (
// content for list of posts - when currentId is null
<>
<h4 className="page-heading">LATEST</h4>
<div className="card-container">
{posts.map((post) => (
<Card
key={post._id}
post={post}
// arrow function takes no arguments but calls `setCurrentId` with this post's id
onClick={() => setCurrentId(post._id)}
/>
))}
</div>
</>
) : (
// content for a single post - when currentId has a value
<>
<div
// setting currentId to null exits the aritcle view
onClick={() => setCurrentId(null)}
>
Back
</div>
<Article
// could pass the whole post
post={posts.find((post) => post._id === currentId)}
// or could just pass the id and `useSelector` in the Article component to select the post from redux
id={currentId}
// can pass a close callback to the component so it can implement its own Back button
onClickBack={() => setCurrentId(null)}
/>
</>
)}
</div>
);
};
To pass in the click hadler the params you want, one could do something like this:
posts.map(post =>
<Card
key={post._id}
post={post}
onClick={() => handleClick(post)} />
)

React rerendering list

I have a react page that has several components on it with a state that shows a modal. I dont want all the components in the app to re render when the modal shows.
const CustomersPage = () => {
const [showModal, setShowModal] = useState(false);
const dataSource = [...omitted data]
return (
<>
<Modal
visible={showModal} />
<div>
<div>
<div>
<Button type="primary" onClick={() => setShowModal(true)}>
Create
</Button>
</div>
<CustomForm />
</div>
<CustomList dataSource={dataSource} />
</div>
</>
);
};
When the showModal value changes, the components CustomForm component and the CustomList component re renders but I dont want them to re render every time the modal shows because the list can have over 100 components. How can I do this?
Edit.
const CustomList = ({ dataSource }) => {
return (
<div>
{dataSource?.map(i => (
<CustomCard
id={i.id}
...other props
/>
))}
</div>
);
};
const CustomCard = ({
... props
}) => {
return (
<>
<Card
...omitted properties
</Card>
</>
);
};
You can wrap the List and Form components in a React.memo component.
This way they will only re-render when their props change their identity.
See:
https://scotch.io/tutorials/react-166-reactmemo-for-functional-components-rendering-control
You can avoid unnecessary re-rendering with memo and hooks like useMemo and useCallback if you are using FC. Or if your are in CC the your create your component pure component that prevent unnecessary render.
Make your function component memo by wrapping component with Reaact.memo, then this will help to check and render component if there is any changes down there in your this child component. Despite all hooks like useCallback and useMemo are also helpfull for optimization.
There are tons of the articles are there about the use cases of these hooks, go and have look at them. They are really helpful.
Thanks

Cannot update a component while rendering a different component when using QueryRender and React context

I am having an issues I can't seem to work out. When I use the QueryRenderer HOC component from relay and render it as children via a react context I get the following error:
Cannot update a component (`Customers`) while rendering a different component (`Search `). To locate the bad setState() call inside `Search`, follow the stack trace as described in https://reactjs.org/link/setstate-in-render
My context and component are very simple. As soon as I replace the QueryRenderer the problem goes away, so I am 100% certain this HOC component is causing the issue.
//context
const SearchProvider = ({ getVariables, query, children }) => {
const [searchTerm, setSearchTerm] = useState()
return (
<SearchContext.Provider value={{ searchTerm, setSearchTerm }}>
<Search getVariables={getVariables} query={query}>
{children}
</Search>
</SearchContext.Provider>
)
}
//component causing the error
const Search = ({ getVariables, query, setRetry, children }) => {
const { searchTerm } = useSearch()
return (
<QueryRenderer
environment={environment}
query={query}
variables={{ ...getVariables(searchTerm) }}
render={({ error, props, retry }) => children({ error, props, retry })}
/>
)
}
//and for context the component that is rendering the SearchProvider component
const Customers = () => {
const getVariables = searchTerm => {
//work out and return variables
}
return (
<>
<h1 className="text-xl mb-2">Customers</h1>
<SearchProvider query={query} getVariables={getVariables}>
{({ error, props }) => {
if (error) return <Error />
if (!props) return <Loading />
return (
<ul className="flex flex-wrap w-full mt-4">
{props.users_connection.edges.map(({ node }) => (
<Customer key={node.userId} customer={node} />
))}
</ul>
)
}}
</SearchProvider >
</>
)
}
Any suggestions or tips are greatly appreciated. For now I am simply living with the error. I haven't noticed it impacting performance or introducing any bugs.
I have tried moving the QueryRenderer component directly in the SearchProvider component and it didn't make any difference.

react higher order component

Im looking into higher order functions and i dont really understand how this part works.
say i have the following function:
const withAdminWarning = WrappedComponent => {
return props => (
<div>
{props.isAdmin && <p>This is private info. Please dont share!</p>}
<WrappedComponent {...props} />
</div>
);
};
const Info = props => (
<div>
<h1>Info</h1>
<p>This info is: {props.info}</p>
</div>
);
const AdminInfo = withAdminWarning(Info);
ReactDOM.render(
<AdminInfo isAdmin={true} info="There are the details" />,
document.getElementById("app")
);
From my understanding of components, to access the props variable, you have to use either props, if its a stateless component, or this.props if it is a class component.
From where does the props come into play in the example above as i cant get access to it from the WrappedComponent or anywhere else apart from the return statement.
The Higher order Component returns a function which is a functional component. Am I right in thinking that foo(Info) means withAdminWarning(Info)?
So after calling withAdminInfo the AdminInfo Component looks basically like:
const AdminInfo = props => (
<div>
{props.isAdmin && <p>This is private info. Please dont share!</p>}
<div>
<h1>Info</h1>
<p>This info is: {props.info}</p>
</div>
</div>
);

Categories

Resources