hossein-zare / react-native-dropdown-picker

A single / multiple, categorizable, customizable, localizable and searchable item picker (drop-down) component for react native which supports both Android & iOS.
https://hossein-zare.github.io/react-native-dropdown-picker-website/
MIT License
970 stars 294 forks source link

Wrapping dropdown in a class component #641

Closed relez closed 10 months ago

relez commented 1 year ago

Hi guys, I am trying to wrap the dropdown in a Class Component to be able to use same configuration across all my apps and no need to repeat same code, also I want it to be compatible with Formik. Here is my code:

import { Component } from "react"; import DropDownPicker from "react-native-dropdown-picker"; import { Field } from "formik";

import appStyles from "../constants/styles"; import COLORS from "../constants/colors"; import Text from "./Text";

class AppDropDown extends Component {
    static defaultProps = {
        schema: {
            label: "Description",
            value: "ID"
        },
        fieldName: "dropdownPicker"
    };

    constructor(props) {
        super(props);
        this.state = {
            open: false,
            items: this.props.items || []
        };
        this.setValue = this.setValue.bind(this);
    }

    componentDidMount() {
        const Theme = require("../constants/pickerTheme");
        DropDownPicker.addTheme("MyTheme", Theme);
        DropDownPicker.setTheme("MyTheme");
    }

    show() {
        this.setState({
            open: true
        });
    }

    hide() {
        this.setState({
            open: false
        });
    }

    setItems(items) {
        this.setState({
            items: items
        });
    }

    setValue(callback) {
        this.setState(state => ({
            value: callback(state.value)
        }));
    }

    render() {
        const { open, items } = this.state;
        const { placeholder, searchable, errorText, errorStyles, schema, fieldName, onChange, style } = this.props;

        return (
            <Field name={fieldName} ref={this.formRef}>
                {({ field: { value }, form: { setFieldValue } }) => (
                    <>
                        <DropDownPicker
                            schema={schema}
                            value={value}
                            items={items}
                            open={open}
                            placeholder={placeholder}
                            searchable={searchable}
                            onSelectItem={item => {
                                setFieldValue(fieldName, item);
                                if (onChange) onChange(item);
                            }}
                            setOpen={open => {
                                this.setState({
                                    open
                                });
                            }}
                            placeholderStyle={{ color: COLORS.PLACEHOLDER }}
                            style={[style]}
                        />
                        {errorText ? <Text style={[appStyles.error, errorStyles]}>{errorText}</Text> : null}
                    </>
                )}
            </Field>
        );
    }
}

export default AppDropDown;

My issue is that once I select the item, it wont change the state of the Dropdown itself and the item wont show up as selected. Formik recognizes it but I need to change the dropdown state. How can I achieve this? Thanks!

taeh98 commented 10 months ago

@relez can you fix your issues following the documentation at https://hossein-zare.github.io/react-native-dropdown-picker-website/docs/usage#class-components? Or does it not help?

relez commented 10 months ago

Hi @taeh98, I have followed the documentation on that URL still can't make it work. Do you have a working example?

taeh98 commented 10 months ago

@relez I haven't been able to make the example work yet. I'm going to continue working on it now.

taeh98 commented 10 months ago

Hi @hossein-zare, I hope you're doing well.

I've adapted the usage example for class components into a finished component and have tried running it in a new (otherwise empty) Expo project. Unfortunately, I'm getting an error: ReferenceError: Property 'setOpen' doesn't exist. Here's the code of the finished version of the usage example:

import DropDownPicker from "react-native-dropdown-picker";
import React, { Component } from "react";
import { View, Text } from "react-native";

export default class JavascriptClassExample extends Component {
  constructor(props) {
    super(props);

    this.state = {
      open: false,
      value: null,
      items: [
        { label: "Bert", value: "Bert" },
        { label: "Darius", value: "Darius" },
        { label: "Maggie", value: "Maggie" },
        { label: "Rebecca", value: "Rebecca" },
        { label: "Ruby", value: "Ruby" },
        { label: "Trent", value: "Trent" },
      ],
    };

    this.setValue = this.setValue.bind(this);
  }

  setOpen(open) {
    this.setState({
      open,
    });
  }

  setValue(callback) {
    this.setState((state) => ({
      value: callback(state.value),
    }));
  }

  setItems(callback) {
    this.setState((state) => ({
      items: callback(state.items),
    }));
  }

  render() {
    const { open, value, items } = this.state;

    return (
      <View
        style={{
          flex: 1,
          flexDirection: "column",
          margin: 3,
          marginTop: 20,
          padding: 3,
        }}
      >
        <View style={{ flex: 1 }}>
          <Text>Choose a name</Text>
        </View>

        <View style={{ flex: 1 }}>
          <DropDownPicker
            open={open}
            value={value}
            items={items}
            setOpen={setOpen}
            setValue={setValue}
            setItems={setItems}
          />
        </View>

        <View style={{ flex: 1 }}>
          <Text>Name currently is: {value}</Text>
        </View>
      </View>
    );
  }
}

If you'd like, I can give you access to the repo itself on here, so you can try running it too. Please could you let me know if you can see anything I'm doing wrong?

I think it might help to add full versions of the examples (e.g. with imports etc) to have a version users can copy and paste and then change as they need. This would help people start using the library more quickly and easily. It might also help to have examples written in TypeScript to help people understand the somewhat confusing types involved, and so TypeScript users can have code to copy and paste and then modify too. Once I can get the example working, I'll probably add them myself.

Thanks! :)

relez commented 10 months ago

Why don't you try?

<DropDownPicker
  open={open}
  value={value}
  items={items}
  setOpen={this.setOpen}
  setValue={this.setValue}
  setItems={this.setItems}
/>
taeh98 commented 10 months ago

setOpen={this.setOpen} setValue={this.setValue} setItems={this.setItems}

Hey @relez, thanks for the reply and the suggestion :) I did actually try that, and it gives you a different error, which will still need fixing. The reason why I put the code as it is above is so that it matches the usage example as closely as possible. Since it doesn't work, that means that the usage example needs to be updated, or even that there's an issue with the library that needs fixing, or both. If you'd like, please feel free to try to get it working and let me know if you can! It is just a new Expo app made with create-expo-app with the following as the App component:

export default function App() {
  return (
    <View style={styles.container}>
      <JavascriptClassExample />
    </View>
  );
}

Thanks :)

hossein-zare commented 10 months ago

Hi @taeh98 @relez @taeh98 You're right, I should correct the example.

Try this example: https://snack.expo.dev/O3wa09_m3

taeh98 commented 10 months ago

Hi @hossein-zare, thanks. I was just about to say I have fixed it too by doing the same. The only functional difference is it works as a demo to users too, showing the value change under the picker:

import DropDownPicker from "react-native-dropdown-picker";
import React, { Component } from "react";
import { View, Text } from "react-native";

export default class JavascriptClassExample extends Component {
  constructor(props) {
    super(props);

    this.state = {
      open: false,
      value: null,
      items: [
        { label: "Bert", value: "Bert" },
        { label: "Darius", value: "Darius" },
        { label: "Maggie", value: "Maggie" },
        { label: "Rebecca", value: "Rebecca" },
        { label: "Ruby", value: "Ruby" },
        { label: "Trent", value: "Trent" },
      ],
    };

    this.setOpen = this.setOpen.bind(this);
    this.setValue = this.setValue.bind(this);
    this.setItems = this.setItems.bind(this);
  }

  setOpen(open) {
    this.setState({
      open,
    });
  }

  setValue(callback) {
    this.setState((state) => ({
      value: callback(state.value),
    }));
  }

  setItems(callback) {
    this.setState((state) => ({
      items: callback(state.items),
    }));
  }

  render() {
    const { open, value, items } = this.state;

    return (
      <View style={{ flex: 1 }}>
        <View style={{ flex: 1 }}>
          <Text>Choose a name</Text>
        </View>

        <View style={{ flex: 1 }}>
          <DropDownPicker
            open={open}
            value={value}
            items={items}
            setOpen={this.setOpen}
            setValue={this.setValue}
            setItems={this.setItems}
            placeholder="Choose a name"
          />
        </View>

        <View style={{ flex: 1 }}>
          <Text>Name currently is: {value}</Text>
        </View>
      </View>
    );
  }
}

I was going to suggest we make a directory of runnable examples that the user can use, maybe as an Expo snack. Really to help people get started, I think there should be some for both JavaScript and TypeScript, class components and functional components, and single-item and multi-item pickers (and in all combinations). Do you think this would be a good idea? I think it could help people get started quickly in the future.

hossein-zare commented 10 months ago

@taeh98 Yep, Good idea. Expo Snacks don't support Typescript, It's not a big deal if we don't cover it.

Are you familiar with Docusaurus 2.0?

taeh98 commented 10 months ago

@taeh98 Yep, Good idea. Expo Snacks don't support Typescript, It's not a big deal if we don't cover it.

Are you familiar with Docusaurus 2.0?

@hossein-zare I use TypeScript, and having up-to date examples with it would have been useful for me. TypeScript is used a lot in big projects to help catch more type errors at compile time, so I think there is a use case! I'll be happy to add TypeScript examples myself :)

I'm not, no

hossein-zare commented 10 months ago

@taeh98 Alright, I think putting the examples in a dedicated directory is better. The directory also needs to be added to the .npmignore file.

taeh98 commented 10 months ago

@taeh98 Alright, I think putting the examples in a dedicated directory is better. The directory also needs to be added to the .npmignore file.

@hossein-zare Okay, cool. So should I make a runnable Expo project as a subdirectory in the main repo with examples, then just add it to .npmignore so it doesn't get bundled with it, then link to it in the readme? That's my plan right now.

hossein-zare commented 10 months ago

@taeh98 Yes, in a examples directory. Here's the structure you can use.

- ./examples
  + ./example-1/
  + ./example-2/

Adding /examples is enough.

hossein-zare commented 10 months ago

@taeh98 I started following you on LinkedIn. Feel free to message me there whenever you need to.

Thanks.

relez commented 10 months ago

Hi guys, great work! But still my main issue is that if I assign default value, for example:

<JavascriptClassExample value={{ label: "Maggie", value: "Maggie" }} />

It won't show the value on the TextInput.

Algo, is it possible to pass a custom TextInput based on my theme/skin of the app to match with regular TextInputs ?? Thanks!

hossein-zare commented 10 months ago

@relez The default value can only be an Integer or String.

For styling the TextInput, you can refer to the documentation at https://hossein-zare.github.io/react-native-dropdown-picker-website/docs/advanced/search#styling

relez commented 10 months ago

Hi @hossein-zare, still if I set an integer or string as value it wont populate the TextInput to show there is a value set, do you get what I am trying to accomplish?

hossein-zare commented 10 months ago

@relez Is there an item with the value in the items array? Create an Expo Snack if possible.

relez commented 10 months ago

Thanks @hossein-zare ... looking carefully at my code I figured it out.

taeh98 commented 10 months ago

For anyone seeking to abstract a DropdownPicker in a class component in the future, you will likely find typescript-class-example.tsx in the examples folder to be useful. You can find it at: https://github.com/hossein-zare/react-native-dropdown-picker/blob/dev-5.x/examples/example-src-files/typescript-class-example.tsx