React add components dynamically from dynamically added component - reactjs

What I am trying to do is to add components iteratively from the last dynamically added component.
|dynamically added component| ----on click----> |added new component| ---on click--> | added another component|
import React, { useState } from 'react';
import Product from '../../Product';
import Project from '../../Project';
const NewProject = () => {
const [productList, setProductList] = useState([]);
const addProduct = () => {
setProductList([...productList, <Product addProduct={addProduct} key={productList.length}></Product>]);
};
return <React.Fragment>
<Project addProduct={addProduct}>
</Project>
{productList}
</React.Fragment>
}
export default NewProject;
Basically, I'm passing addProduct const to the Product component and it executes the addProduct function again. I see addProduct is triggered but I don't see component came. I think I'm mixing some states there.
Adding Product component from Project component props works fine by the way. But I want to do the same with added product components.

I don't know the exact problem but the way I use it is a little bit different instead of saving Product Component to the list, I only save the product data then I create a another function to render them into components and then pass it in return like below:
import React, { useState } from 'react';
import Product from '../../Product';
import Project from '../../Project';
const NewProject = () => {
const [products, setProducts] = useState([{}]);
const addProduct = () => {
setProducts([...products, {}]);
};
const renderProducts = () => products.map((_, i) => <Product addProduct={addProduct}/>)
return <>{renderProducts()}</>;
};
export default NewProject;

Related

Can you use setReduxObject and selectReduxObject in the same .jsx page?

I am learning Redux and I have deviated from the instructor's code. I am trying to convert my code from context & state into Redux.
Is it advisable to use setReduxObject (setCategoriesMap in my code) and selectReduxObject (selectCategoriesMap in my code) in the same .jsx page? Are there any concerns around this?
Thanks!
My code:
import { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { getCategoriesAndDocuments } from "../../utils/firebase/firebase.utils";
import { setCategoriesMap } from "../../store/categories/categories.action";
import { selectCategoriesMap } from "../../store/categories/categories.selector";
import Category from "../../components/category/category.component";
const Shop = () => {
const dispatch = useDispatch();
useEffect(() => {
const getCategoriesMap = async () => {
const categories = await getCategoriesAndDocuments();
dispatch(setCategoriesMap(categories));
};
getCategoriesMap();
}, []);
const categoriesMap = useSelector(selectCategoriesMap);
return (
<div>
{Object.keys(categoriesMap).map((key) => {
const products = categoriesMap[key];
return <Category key={key} title={key} products={products} />;
})}
</div>
);
};
export default Shop;
This is just the default approach, nothing to be concerned about.
As soon as you're using getCategoriesAndDocuments the same way in another component though, it's better to move this to an async action creator.
Could even do it for this component already to improve separation of concerns. The component does not necessarily need to be involved with firebase, its job is display logic. Wether the data comes from firebase or localStorage or some graphQL server should not matter.

React.js - passing functions between components using Context API - not working

I am trying to pass a function between two components but even though I do not have any errors, the function that I am passing wont show or to be precise it is not working. I have two files and one of them is creating a context while the other is using it (obviously). Now, they are not shown in App.js (which is rendered in index.js, usual stuff) they are in the seperate folder. I am using React Router to show one the pages (News.js).
Here are the files:
NewsContext.js
import React, { useContext, createContext, useState, useEffect } from "react";
export const newsK = React.createContext();
export const NewsContext = (props) => {
const working = () => {
console.log("it is working");
};
return <newsK.Provider value={working}>{props.children}</newsK.Provider>;
};
export default NewsContext;
News.js
import React, { useContext, useState, useEffect } from "react";
import { newsK } from "./NewsContext";
import { NewsContext } from "./NewsContext";
const News = () => {
const data = useContext(newsK);
return (
<NewsContext>
<div>
<button onClick={data}></button>
</div>
</NewsContext>
);
};
export default News;
When i pressed the button, it wont do anything. Any tips?
You're trying to use context outside the NewsContext component
The solution for this will be to create a component that will call useContext inside NewsContext.
I.e.
const WrappedButton = () => {
const data = useContext(newsK)
return <button onClick={data} />
}
And then put it inside the NewsContext:
<NewsContext>
<div>
<WrappedButton />
</div>
</NewsContext>

On click returns null instead of an object

It's really basic I guess. I'm trying to add onClick callback to my script & I believe I'm missing a value that would be responsible for finding the actual item.
Main script
import React from 'react';
import { CSVLink } from 'react-csv';
import { data } from 'constants/data';
import GetAppIcon from '#material-ui/icons/GetApp';
import PropTypes from 'prop-types';
const handleClick = (callback) => {
callback(callback);
};
const DownloadData = (props) => {
const { callback } = props;
return (
<>
<CSVLink
data={data}
onClick={() => handleClick(callback)}
>
<GetAppIcon />
</CSVLink>
</>
);
};
DownloadData.propTypes = {
callback: PropTypes.func.isRequired,
};
export default DownloadData;
Storybook code
import React from 'react';
import DownloadData from 'common/components/DownloadData';
import { data } from 'constants/data';
import { action } from '#storybook/addon-actions';
export default {
title: 'DownloadData',
component: DownloadData,
};
export const download = () => (
<DownloadData
data={data}
callback={action('icon-clicked')}
/>
);
So right now with this code on click in the storybook I'd get null and I'm looking for an object.
One of the potential issues I can see is that your handleClick function is stored as it is in-memory, when you import the component. That means you're keeping reference of something that doesn't exists and expects to use it when rendering the component with the callback prop.
Each instance of a component should have its own function. To fix it, move the function declaration inside the component. Like this:
const Foo = ({ callback }) => {
// handleClick needs to be inside here
const handleClick = callback => {
console.log("clicked");
callback(callback);
};
return <div onClick={() => handleClick(callback)}>Click me!</div>;
};
Check this example.
If this doesn't fix your problem, then there is something wrong with how you're implementing Storybook. Like a missing context.

Test react components with jest and enzyme and css modules

I have a project which was created using create react app.
Lets say I have a simple component like this -
import React from 'react';
import React, { useState } from "react";
function Example() {
const [data, setData] = useState(0);
const onClickHandler = () => {
setData(data + 1);
};
return (
<div className="button" onClick={onClickHandler}>
{data}
</div>
);
}
export default Example;
I will test the component like this -
import React from "react";
import { shallow } from "enzyme";
import Example from "./Example";
it("example test", () => {
const wrraper = shallow(<Example />);
wrraper.find(".button").simulate("click");
expect("test somethig");
});
If I will use styles.module like this -
import React, { useState } from "react";
import styles from "./styles.module.scss";
function Example() {
const [data, setData] = useState(0);
const onClickHandler = () => {
setData(data + 1);
};
return (
<div className={styles.button} onClick={onClickHandler}>
{data}
</div>
);
}
export default Example;
I will not be able to find and element in the test using ".button" anymore, since webpack will add a hash to my class name when I am using css modules.
So how can I test react compoennt while using css modules? Only by adding an Id to the element?
It fills wrong to change my code so I will be able to test it.
There are many alternative selectors, which you can find here. That said, when working with (s)css modules, I tend to lean on the element position within the component:
wrapper.find("div").first() will select the first div element within the component heirarchy (or in your example, it'll select the div with the "styles.button" className).
Another alternative is to use template literals. With the example below, I essentially created an escape hatch to select .some-classname:
import React, { useCallback, useState } from "react";
import { button } from "./styles.module.scss";
function Example() {
const [data, setData] = useState(0);
const onClickHandler = useCallback(() => {
setData(prevSate => prevState + 1);
}, [setData]);
return (
<div className={`${button} some-classname`} onClick={onClickHandler}>
{data}
</div>
);
}
export default Example;
And lastly, you can use a data-attribute -- like data-test-id (which is becoming more popular because of react-testing-library) to create a simple static selector that can be removed with an additional babel plugin for production builds).
I'm kinda late to the party but I was wondering this myself. You just have to import your scss module into your test file, like this :
import React from "react";
import { shallow } from "enzyme";
import Example from "./Example";
import styles from "./styles.module.scss
it("example test", () => {
const wrraper = shallow(<Example />);
wraper.find(`.${styles.button}`).simulate("click");
expect("test somethig");
});

reactjs data mapping, get data from server

I'm new reactjs
I'm trying to save data that I got from server like object(array) but I can't.
at render() function, what should I do to save data, I don't wanna display, just save to users (array) or something? I think that I should use "map" but I don't know how to do.
Next, I wanna save users to model.data like this. help me.
Since you just started using react, try using React Hooks instead of class style components. It's the recommended way.
If you just want to store the data without displaying anything you need somekind of a encapsulated/shared state. For example redux or context can help you with that. Since context is in-built and easier to use, here is an example:
First create a context
users-context.js
import React from "react";
export const UsersContext= React.createContext();
Now create a custom hook to store your state.
useUsers.js
import React, {useState, useEffect} from "react";
export const useUsers = () => {
const [users, setUsers] = useState([]);
const getUsers = () =>{
//your fetch
}
useEffect(()=>{ //equivalent to componentDidMount
getUsers();
}, [])
return {users, setUsers}
}
Then provide the context so every component in your app has access to that context.
App.jsx
import {UsersContext} from "./UsersContext";
const App = () => {
const contextValue = useUsers();
return (
<div className={'App'}>
<UsersContext.Provider value={contextValue}>
<Main/>
</UsersContext.Provider>
</div>
);
};
export default App;
If you want to use the state in a component, e.g. a profile page do this:
profile-page.jsx
import React, {useContext} from "react";
import {UsersContext} from "./UsersContext";
const ProfilePage = () => {
const {users} = useContext(UsersContext);
// now you can use it like
console.log(users)
return (...)
}
import { UsersContext } from './Components/usersData/users-context';
const getUsers = () => {
const {users} = UsersContext(UsersContext);
console.log(users);
return users;
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
data:[]
}
data.push(getUsers);`
}
}

Resources